from pml import PML
from pml import Objects
from pml import Errors
from pml.Bytecode import constants
from pml.Bytecode.BytecodeBuilder import Bytecode
from pml.Parser.lexer import constants as TokenConstants
from pml import Eval
import os, pickle

__all__ = 'Runner',

def ExecuteTheBytecodeOfBinaryOperation(frame, stack, operationFunction, position, Frame=None):
    if operationFunction == TokenConstants.TV_SET_VALUE:
        value = stack.top(frame, stack, position=None)
        stack.pop(frame, stack, position=None)
        name = stack.top(frame, stack, position=None)
        stack.pop(frame, stack, position=None)
        stack.push(frame, stack, None, value)
        setVariable(Frame, Frame, name, value)
    else:
        right = stack.top(frame, stack, position=None)
        stack.pop(frame, stack, position=None)
        left = stack.top(frame, stack, position=None)
        stack.pop(frame, stack, position=None)
        stack.push(frame, stack, None, PML.tool.CallFunction.CallFunction(frame, left, left.getMember(operationFunction), position, right))
def ExecuteTheBytecodeOfUnaryOperation(left, frame, stack, operationFunction, position):
    right = stack.top(frame, stack, position=None)
    stack.pop(frame, stack, position=None)
    function = ('__left' if left else '__right') + operationFunction
    stack.push(frame, stack, PML.tool.CallFunction.CallFunction(frame, right, right.getMember(function), position))

def getVariable(frame, name):
    try:
        try:
            return frame.const[name]
        except KeyError:
            return frame.variable[name]
    except KeyError:
        if frame.father is None:
            raise NameError
        else:
            return getVariable(frame.father, name)

def setVariable(rootFrame, frame, name, value):
    try:
        frame.variable[name]
        frame.variable[name] = value
    except KeyError:
        if frame.father is None:
            rootFrame.variable[name] = value
        else:
            setVariable(rootFrame, frame.father, name, value)

class Runner(Objects.object.typeobject):
    def __init__(self, frame=None, begin=True, this=None, position=None):
        super().__init__('Runner')
        self.frame = frame
        self.begin = begin
        self.this = this
        self.thisTemp = None
        self.error = None
        self.errorFrame = None
        self.position = position

    def getObject(self):
        self.methods['__init__'] = self.tp_init

        self.methods['run'] = self.run

        return self

    @staticmethod
    def run(frame, this: 'Runner', position):
        buildBlock = False
        buildBlockCnt = 0
        buildBytecodes = []

        try:
            bytecode = this.frame.getNextBytecode(frame, this.frame, position=None)
            bytecode = bytecode if isinstance(bytecode, Bytecode) else bytecode.obj
        except StopIteration:
            return
        while True:
            if buildBlock:
                if bytecode.operator == constants.BEGIN_BLOCK:
                    buildBlockCnt += 1
                    buildBytecodes.append(bytecode)
                elif bytecode.operator == constants.END_BLOCK:
                    if buildBlockCnt == 1:
                        buildBlock = False
                        this.frame.stack.push(frame, this.frame.stack, None, buildBytecodes)
                        buildBytecodes = []
                        try:
                            bytecode = this.frame.getNextBytecode(frame, this.frame, position=None)
                            bytecode = bytecode if isinstance(bytecode, Bytecode) else bytecode.obj
                        except StopIteration:
                            return
                        continue
                    else:
                        buildBlockCnt -= 1
                        buildBytecodes.append(bytecode)
                else:
                    buildBytecodes.append(bytecode)
            elif bytecode.operator == constants.EXIT:
                break


            elif bytecode.operator == constants.LOAD:
                if bytecode.value[0] == TokenConstants.TT_INT:
                    this.frame.stack.push(frame, this.frame.stack, None, Objects.object.PMTypeObject(Objects.PMIntegerObject.PMIntegerObject(int(bytecode.value[1])).getObject()))
                elif bytecode.value[0] == TokenConstants.TT_STRING:
                    this.frame.stack.push(frame, this.frame.stack, None, Objects.object.PMTypeObject(Objects.PMStringObject.PMStringObject(bytecode.value[1]).getObject()))

            elif bytecode.operator == constants.LEFT_UNARY_OPERATOR:
                if bytecode.value == TokenConstants.TV_ADD:
                    ExecuteTheBytecodeOfUnaryOperation(True, this.frame, this.frame.stack, '_positive__', this.position)
                elif bytecode.value == TokenConstants.TV_SUB:
                    ExecuteTheBytecodeOfUnaryOperation(True, this.frame, this.frame.stack, '_negative__', this.position)
            elif bytecode.operator == constants.BINARY_OPERATOR:
                if bytecode.value == TokenConstants.TV_ADD:
                    ExecuteTheBytecodeOfBinaryOperation(this.frame, this.frame.stack, '__addition__', this.position, this.frame)
                elif bytecode.value == TokenConstants.TV_SUB:
                    ExecuteTheBytecodeOfBinaryOperation(this.frame, this.frame.stack, '__subtraction__', this.position, this.frame)
                elif bytecode.value == TokenConstants.TV_MUL:
                    ExecuteTheBytecodeOfBinaryOperation(this.frame, this.frame.stack, '__multiplication__', this.position, this.frame)
                elif bytecode.value == TokenConstants.TV_DIV:
                    ExecuteTheBytecodeOfBinaryOperation(this.frame, this.frame.stack, '__division__', this.position, this.frame)
                elif bytecode.value == TokenConstants.TV_SET_VALUE:
                    ExecuteTheBytecodeOfBinaryOperation(this.frame, this.frame.stack, TokenConstants.TV_SET_VALUE, this.position, this.frame)

                elif bytecode.value == TokenConstants.TV_EQ:
                    ExecuteTheBytecodeOfBinaryOperation(this.frame, this.frame.stack, '__eq__', this.position, this.frame)
            elif bytecode.operator == constants.LOAD_NAME:
                name = this.frame.stack.top(frame, this.frame.stack, position=None)
                this.frame.stack.pop(frame, this.frame.stack, position=None)
                try:
                    if bytecode.value:
                        father = this.frame.stack.top(frame, this.frame.stack, position=None)
                        this.frame.stack.pop(frame, this.frame.stack, position=None)
                        if isinstance(father, dict):
                            this.frame.stack.push(frame, this.frame.stack, None, father[name])
                        else:
                            try:
                                this.frame.stack.push(frame, this.frame.stack, None, father.getMember(name))
                                this.thisTemp = this.this
                                this.this = father
                            except NameError:
                                try:
                                    this.frame.stack.push(frame, this.frame.stack, None, father.getStaticMember(name))
                                    this.thisTemp = this.this
                                    this.this = father
                                except NameError as e:
                                    raise NameError(e)
                    else:
                        this.frame.stack.push(frame, this.frame.stack, None, getVariable(this.frame, name))
                except NameError as e:
                    raise NameError(e)
                    this.error = Objects.object.PMTypeObject(Errors.PMNameError.PMNameError(f'{name} isn\'t defined.').getObject())
                    this.errorFrame = this.frame
                    break

            elif bytecode.operator == constants.LOAD_ID:
                if bytecode.value[0]:
                    father = this.frame.stack.top(frame, this.frame.stack, position=None) + '.'
                    this.frame.stack.pop(frame, this.frame.stack, position=None)
                else:
                    father = ''
                this.frame.stack.push(frame, this.frame.stack, None, None if bytecode.value[1] is None else father + bytecode.value[1])
            elif bytecode.operator == constants.LOAD_CONST:
                this.frame.stack.push(frame, this.frame.stack, None, bytecode.value)

            elif bytecode.operator == constants.STORE_NAME:
                name = this.frame.stack.top(frame, this.frame.stack, position=None)
                this.frame.stack.pop(frame, this.frame.stack, position=None)
                value = this.frame.stack.top(frame, this.frame.stack, position=None)
                this.frame.stack.pop(frame, this.frame.stack, position=None)
                if bytecode.value == 'let':
                    this.frame.variable[name] = value
                elif bytecode.value == 'variable':
                    f = this.frame
                    while f.father is not None:
                        f = f.father
                    f.variable[name] = value
                else:
                    if name in this.frame.const:
                        # error
                        pass
                    this.frame.const[name] = value

            elif bytecode.operator == constants.BEGIN_BLOCK:
                buildBlockCnt = 1
                buildBlock = True
            elif bytecode.operator == constants.BUILD_BLOCK:
                name = this.frame.stack.top(frame, this.frame.stack, position=None)
                this.frame.stack.pop(frame, this.frame.stack, position=None)
                bytecodes = this.frame.stack.top(frame, this.frame.stack, position=None)
                this.frame.stack.pop(frame, this.frame.stack, position=None)
                this.frame.stack.push(frame, this.frame.stack, None, Eval.frame.Frame(this.frame.name if name is None else name, bytecodes, Eval.stack.Stack(), {}, {}, father=this.frame, stdio=this.frame.stdio))
            elif bytecode.operator == constants.RUN_BLOCK:
                frame = this.frame.stack.top(frame, this.frame.stack, position=None)
                this.frame.stack.pop(frame, this.frame.stack, position=None)
                runner = Runner(frame, begin=False, this=this.this, position=this.position)
                runner.run(frame, runner, position=None)
                del runner.frame
                if runner.error is not None:
                    this.error = runner.error
                    this.errorFrame = runner.errorFrame
                    break
                del runner

            elif bytecode.operator == constants.FOR:
                loopVariable = this.frame.stack.top(frame, this.frame.stack, position=None)
                this.frame.stack.pop(frame, this.frame.stack, position=None)
                iter = this.frame.stack.top(frame, this.frame.stack, position=None)
                this.frame.stack.pop(frame, this.frame.stack, position=None)
                body = this.frame.stack.top(frame, this.frame.stack, position=None)
                this.frame.stack.pop(frame, this.frame.stack, position=None)
                runner = Runner(body, begin=False, this=this.this, position=this.position)
                for loop in iter:
                    body.variable[loopVariable] = loop
                    body.index = -1
                    body.stack.clear(frame, body.stack)
                    runner.run(frame, runner, position=None)
                del runner.frame
                del runner

            elif bytecode.operator == constants.FUNCTION:
                name = this.frame.stack.top(frame, this.frame.stack, position=None)
                this.frame.stack.pop(frame, this.frame.stack, position=None)
                count = this.frame.stack.top(frame, this.frame.stack, position=None)
                this.frame.stack.pop(frame, this.frame.stack, position=None)
                args = []
                for i in range(count):
                    args.append(this.frame.stack.top(frame, this.frame.stack, position=None))
                    this.frame.stack.pop(frame, this.frame.stack, position=None)
                body = this.frame.stack.top(frame, this.frame.stack, position=None)
                this.frame.stack.pop(frame, this.frame.stack, position=None)
                this.frame.variable[name] = Objects.object.PMTypeObject(Objects.PMFunctionObject.PMFunctionObject(body, name, *args).getObject())
            elif bytecode.operator == constants.CALL:
                count = this.frame.stack.top(frame, this.frame.stack, position=None)
                this.frame.stack.pop(frame, this.frame.stack, position=None)
                args = []
                for i in range(count):
                    args.append(this.frame.stack.top(frame, this.frame.stack, position=None))
                    this.frame.stack.pop(frame, this.frame.stack, position=None)
                function = this.frame.stack.top(frame, this.frame.stack, position=None)
                this.frame.stack.pop(frame, this.frame.stack, position=None)
                if isinstance(function, Objects.object.typeobject) and not isinstance(function, Objects.PMFunctionObject.PMFunctionObject):
                    obj = PML.object.PML_RealizePMLObject(function)
                    PML.tool.CallFunction.CallFunction(this.frame, obj.obj, obj.getMember('__init__'), this.position, *args)
                    this.frame.stack.push(frame, this.frame.stack, None, obj)
                else:
                    try:
                        ret = PML.tool.CallFunction.CallFunction(this.frame, this.this, function, this.position, *args)
                        this.this = this.thisTemp
                    except TypeError:
                        raise TypeError
                    if isinstance(ret, list):
                        if ret[0] is None:
                            this.frame.stack.push(frame, this.frame.stack, None, ret[1])
                        else:
                            this.error = ret[0]
                            this.errorFrame = ret[1]
                            break
                    else:
                        this.frame.stack.push(frame, this.frame.stack, None, ret)

            elif bytecode.operator == constants.JUMP:
                this.frame.jump(frame, this.frame, position, bytecode.value - 1)
            elif bytecode.operator == constants.IF_TRUE_JUMP:
                expression = this.frame.stack.top(frame, this.frame.stack, position=None)
                this.frame.stack.pop(frame, this.frame.stack, position=None)
                if PML.tool.CallFunction.CallFunctionAndCheckReturnType(this.frame, expression, expression.getMember('__boolean__'), this.position, type=Objects.PMBooleanObject.PMBooleanObject).obj.value:
                    this.frame.jump(frame, this.frame, position, bytecode.value - 1)
            elif bytecode.operator == constants.IF_FALSE_JUMP:
                expression = this.frame.stack.top(frame, this.frame.stack, position=None)
                this.frame.stack.pop(frame, this.frame.stack, position=None)
                if not PML.tool.CallFunction.CallFunctionAndCheckReturnType(this.frame, expression, expression.getMember('__boolean__'), this.position, type=Objects.PMBooleanObject.PMBooleanObject).obj.value:
                    this.frame.jump(frame, this.frame, position, bytecode.value - 1)

            elif bytecode.operator == constants.IMPORT:
                name = this.frame.stack.top(frame, this.frame.stack, position=None)
                this.frame.stack.pop(frame, this.frame.stack, position=None)
                modulePath = os.path.join(this.position, name.replace('.', '\\') + '.pmm')
                try:
                    with open(modulePath, 'rb') as o:
                        moduleBytecodes = pickle.load(o)
                except (FileNotFoundError, OSError):
                    try:
                        modulePath = os.path.join('/'.join([i for i in __file__.split('\\')[:-2]]), 'Lib', name.replace('.', '\\') + '.pmm')
                        with open(modulePath, 'rb') as o:
                            moduleBytecodes = pickle.load(o)
                    except (FileNotFoundError, OSError):
                        raise NameError(modulePath, name)
                moduleRunner = Runner(Eval.frame.Frame(modulePath, moduleBytecodes, Eval.stack.Stack(), {}, {}))
                moduleRunner.run(frame, moduleRunner, position=None)
                if bytecode.value:
                    for variable in moduleRunner.frame.variable:
                        this.frame.variable[variable] = moduleRunner.frame.variable[variable]
                    for constant in moduleRunner.frame.const:
                        this.frame.const[constant] = moduleRunner.frame.const[constant]
                else:
                    this.frame.const[name.split('.')[-1]] = Eval.frame.Frame(name.split('.')[-1], variable=moduleRunner.frame.variable, const=moduleRunner.frame.const)

            elif bytecode.operator == constants.PYTHON:
                namespace = {}
                exec(bytecode.value if isinstance(bytecode.value, str) else bytecode.value.obj.value, namespace, namespace)
                if 'PML_MODULE' in namespace:
                    v = namespace['PML_MODULE'].variable
                    c = namespace['PML_MODULE'].constant
                    for var in v:
                        this.frame.variable[var] = v[var]
                    for const in c:
                        this.frame.const[const] = c[const]

            try:
                bytecode = this.frame.getNextBytecode(frame, this.frame, position=None)
                bytecode = bytecode if isinstance(bytecode, Bytecode) else bytecode.obj
            except StopIteration:
                return


    @staticmethod
    def tp_init(frame, this, position):
        pass
